iT邦幫忙

2022 iThome 鐵人賽

DAY 30
0
自我挑戰組

文科生轉職React前端工程師的菜鳥學習日記系列 第 30

[Day30] 菜鳥學習日記,React的To-Do List專案Part6

  • 分享至 

  • xImage
  •  

我的30天鐵人賽最後一天終於來了!!!
今天要來介紹的是To-Do List內的update更新功能,讓我們繼續看下去,這邊附上完整的程式碼連結在codesandbox

import React, { useState } from 'react'
import "./App.css"

const App = () => {
  // 初始化我們input輸入框為空字串
  const [inputToDo, setInputToDo] = useState("")
  // 初始化我們待辦清單的所有內容為空陣列
  const [toDoList, setToDoList] = useState([])
  // 確認我們待辦事項的編輯狀態,初始Id為0
  const [updateId, setUpdateId] = useState(0)
  // 新增待辦事項的按鈕函數
  const handleSubmit = (e) => {
    // 當我們點選Add按紐時handleSubmit會被觸發
    // 我們不希望它刷新頁面
    e.preventDefault();
    if (updateId) {
      // 要用找到的id內的值回傳到輸入框並來編輯它
      // 先確認跟我們要尋找的Id是否相符
      // 它會詢問我們正在嘗試更新的待辦事項
      // 如果"是"已更新的,我們提供原始的Id並執行Input寫入的任何內容
      // 如果"不是",我們就提供它的默認值
      const updateToDo = toDoList.find((i) => i.id === updateId);
      const updatedToDoList = toDoList.map((t) => t.id === updateToDo.id 
      ? (t = { id: t.id, inputToDo }) : {id: t.id, inputToDo: t.inputToDo}
      );
      setToDoList(updatedToDoList);
      setUpdateId(0);
      setInputToDo("");
      return
    }
    // 只要inputToDo裡面不是空字串
    // 就會創建新的To-Do List
    if (inputToDo !== "") {
      setToDoList([{ id: `${inputToDo}-${Date.now()}`, inputToDo }, ...toDoList])
      // 輸入框新增按鈕送出後輸入框內的文字為空,記得下方input要寫value={inputToDo}
      setInputToDo("");
    }

  };
    // 控制delete按鈕的函式
    // 將delete裡面的所有東西都做變量
    // 設置filter過濾器去抓清單的獨特id
    // 它將比較所有內容,如果與這個id匹配
    // 那麼它就會被刪除
    // 否則,如果不匹配這邊給不等於!==
    // 它將不會被刪除
  const handleDelete = (id) => {
    const deleteToDo = toDoList.filter((to) => to.id !== id);
    // 最後狀態需要更新回傳,將刪除傳遞給array
    // ...為擴展運算符號
    setToDoList([...deleteToDo]);
  };
    // 控制update按鈕的函式
    // 需求為按下update按鈕後
    // 更新前的文字會出現在輸入框內
    // 我們做修改後送出會更新我們原本的To-Do List

    // 用find找到我們單一的To-Do
      // 讓它透過id去尋找我們要修改相同id的元素
      // 它將回傳具有相同id的整個對象和To-Do內容在輸入框內
      // 透過find尋找所有的To-Do list的array中的特定id相等就會回傳

      // 接下來就是我們要修改的內容為setInputToDo
      // updateTodo是一個object裡面包含一個id和一個To-Do
    const handleUpdate = (id) => {
      const updateToDo = toDoList.find((i) => i.id === id);
      setInputToDo(updateToDo.inpuToDo);
      setUpdateId(id);
    };

  return (
    <div className="App">
      <div className='container'>
        {/* 標題 */}
        <h1>To-Do List</h1>
        {/* 待辦事項的輸入表單,提交按鈕觸發handleSubmit函式 */}
        <form className='toDoForm' onSubmit={handleSubmit}>
          {/* 新增待辦事項的輸入框,onChange每當輸入框改變時調用,value讓它等於{inputToDo},輸入新增按鈕後就會輸入框就會變成空 */}
          <input type="text" value={inputToDo} onChange={(e) => setInputToDo(e.target.value)}/>
          {/* 新增Add按鈕和修改Update的輸入框共用,UpdateId如果"有"返回"Update,否則返回Add*/}
          <button type="sumbit">{updateId ? "Update" : "Add"}</button>
        </form>
        {/* 待辦清單顯示內容,t代表整個對象為輸入框內的inpuiToDo值 */}
        <ul className='allToDoList'>
          {toDoList.map((t) => (
             <li className='singleTodoTask' >
              <span className='toDoText' key={t.id}>{t.inputToDo}</span>
             {/* 創建更新按鈕要傳入的函式 */}
             <button type="button" onClick={() => handleUpdate(t.id)}>Update</button>
             {/* 創建刪除按鈕要傳入的函式 */}
             <button type="button" onClick={() => handleDelete(t.id)}>Delete</button>
           </li>
          ))}
        </ul>
      </div>
    </div>
  )
}

export default App

https://ithelp.ithome.com.tw/upload/images/20221015/20142045m4jWSGSNtV.png
https://ithelp.ithome.com.tw/upload/images/20221015/20142045XsWQpoBNrz.png

後面補上React元件(Component)拆分檔案的教學,如何透過props從子層傳入父層,先來介紹它的概念。

Components 與 Props

Component 使你可以將 UI 拆分成獨立且可複用的程式碼,並且專注於各別程式碼的思考。
概念上來說,component 就像是 JavaScript 的 function,它接收任意的參數(稱之為「props」)並且回傳描述畫面的 React element。

Function Component 與 Class Component
定義 component 最簡單的方法即是撰寫一個 Javascript function:

function Greeting(props) {
  return <h1>Hello, {props.name}</h1>;
}

此 function 是一個符合規範的 React component,因為它接受一個「props」(指屬性 properties)物件並回傳一個 React element。我們稱之為 function component,因為它本身就是一個 JavaScript function。

同樣的,也可以使用 ES6 Class 來定義 component:

class Greeting extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

上面兩種 component 在 React 中都是一樣的只是寫法不同。

React 元件拆分檔案範例

這邊來介紹另外一個To-Do List的寫法,使用useState和useRef方法,並將元件內的檔案做拆分,完整程式碼在codesandbox這邊的連結

App.js

import { useRef, useState } from "react";
import ToDo from "./components/ToDo";
import "./styles.css";
// 原始To-Do資料結構
const todos = [
  {
    id: 1,
    content: "do the dishes",
    checked: true
  }
];
export default function App() {
  // 使用 useState 鉤子建立一個狀態變數 list 用來追蹤待辦事項的列表
  // 初始值設定為 todos
  const [list, setList] = useState(todos);
  // isUpdated, setIsUpdated拆分到components > ToDo.js
  // const [isUpdated, setIsUpdated] = useState(false);

  // ref 的作法
  // updateRef拆分到components > ToDo.js
  // const updateRef = useRef(null);
  // 建立一個 createdRef 使用 useRef 鉤子 將它設定為 null 
  // 將用於處理新待辦事項的輸入
  const createdRef = useRef(null);
  // onChecked 函數處理待辦事項的勾選狀態更新
  const onChecked = (e) => {
    const checkedTarget = list.find((el) => el.id === parseInt(e.target.id));
    checkedTarget.checked = e.target.checked;
    setList([...list]);
  };
  // onUpdate 函數處理待辦事項的內容更新
  const onUpdate = (e) => {
    const contentTarget = list.find((el) => el.id === parseInt(e.target.id));
    contentTarget.content = e.target.value;
    setList([...list]);
    console.log(e.target.value);
  };
  // handleIsUpdated拆分到components > ToDo.js
  // const handleIsUpdated = () => {
  //   setIsUpdated(!isUpdated);
  // };

  // confirmUpdated拆分到components > ToDo.js
  // const confirmUpdated = () => {
  //   console.log(updateRef.current.value);
  //   const contentTarget = list.find(
  //     (el) => el.id === parseInt(updateRef.current.id)
  //   );
  //   contentTarget.content = updateRef.current.value;
  //   setList([...list]);
  // };
  // handleCreated 函數處理新建待辦事項的邏輯 並將其添加到列表中
  const handleCreated = () => {
    console.log(createdRef.current.value);
    const newToDo = {
      id: list[list.length - 1].id + 1,
      content: createdRef.current.value,
      checked: false
    };

    setList([...list, newToDo]);
  };
  // 在 return 中建立了一個包含 To-Do List 的 React 元件
  // 包括輸入欄位、按鈕和待辦事項列表。
  // 列表中的每個待辦事項都是使用 ToDo 組件來表示 並傳遞了必要的屬性。
  return (
    <div className="App">
      <div className="container">
        <h1>To-Do List</h1>
        <div className="to_do_create">
          <input className="inputText" type="text" ref={createdRef} />
          <button type="button onClick={handleCreated}>Create</button>
        </div>
        <ol className="all_to_do_list">
          {list.map((todo) => (
            // 從子層ToDo.js中傳入修改及確認按鈕的元件到父層App.js
            <ToDo key={todo.id} todo={todo} list={list} setList={setList} />
          ))}
        </ol>
      </div>
    </div>
  );
}

components > ToDo.js

import { useRef, useState } from "react";
import "./ToDo.css";
// 定義ToDo組件,接受App.js的 todo、list 和 setList 作為屬性
const ToDo = ({ todo, list, setList }) => {
  // 使用 useState 鉤子建立一個狀態變數 isUpdated 來追蹤是否處於待辦事項更新模式
  const [isUpdated, setIsUpdated] = useState(false);
  // ref 的作法
  // 使用 useRef 鉤子建立一個 updateRef 它將用於更新待辦事項的內容
  const updateRef = useRef(null);
  // onChecked 函數處理待辦事項的勾選狀態更新,與 App.js 中的相同
  const onChecked = (e) => {
    const checkedTarget = list.find((el) => el.id === parseInt(e.target.id));
    checkedTarget.checked = e.target.checked;
    setList([...list]);
  };
  // handleIsUpdated 函數處理進入或退出待辦事項更新模式 切換 isUpdated 狀態
  const handleIsUpdated = () => {
    setIsUpdated(!isUpdated);
  };
  // confirmUpdated 函數處理確認待辦事項內容更新 同時將 isUpdated 狀態切換回原來的模式
  const confirmUpdated = () => {
    console.log(updateRef.current.value);
    const contentTarget = list.find(
      (el) => el.id === parseInt(updateRef.current.id)
    );
    contentTarget.content = updateRef.current.value;
    setList([...list]);
    handleIsUpdated();
  };
  // 在 return 中,根據 isUpdated 狀態來顯示待辦事項的文字或輸入欄位 
  // 以及更新和確認按鈕
  return (
    <li className="single_to_do_task" key={todo.id}>
      <div className="to_do_text">
        {isUpdated ? (
          <input
            id={todo.id}
            // value={todo.content}
            // onChange={onUpdate}
            ref={updateRef}
          />
        ) : (
          <p>{todo.content}</p>
        )}
        <input
          id={todo.id}
          type="checkbox"
          checked={todo.checked}
          onChange={onChecked}
        />
        {!isUpdated && <button type="button" onClick={handleIsUpdated}>Update</button>}
        {isUpdated && <button type="button" onClick={confirmUpdated}>Confirm</button>}
      </div>
    </li>
  );
};
export default ToDo;

參賽結語

謝謝itHome的平台,敦促我完成這次三十天技術文章的鐵人賽挑戰,這是我第一次參加鐵人賽,原先只有初步的規劃,沒有特別的準備,幾乎都是當天完成上傳的,所以有點計畫趕不上變化,原本預計是想寫包含API串接的練習,這次最後只完成了前端畫面的新增、修改、刪除、查閱,前面主要都著重在轉職和學習資源的分享,也發現自己還有很多的不足需要再持續精進;這次也看到好多好棒的參賽文章,都說新的習慣養成需要二十一天,我之後也會盡量維持寫技術文章的習慣,一天進步一點點;期許自己下一次參加鐵人賽,一定要做好規劃,準備好主題和文章庫存,不然像這次遇到半路殺出的緊急狀況就差點無法應變了。

如果有錯誤歡迎指正,我會盡快修改。

參考資料:
React Components 與 Props


上一篇
[Day29] 菜鳥學習日記,React的To-Do List專案Part5
系列文
文科生轉職React前端工程師的菜鳥學習日記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
behold
iT邦新手 5 級 ‧ 2023-02-26 15:29:36

前面多篇快速看過,認真看是從IA開始看,看著看著才發現這文章很有內容,尤其有提供大量的資料補充。讓我想到其實開發網站像我待的小公司這樣,一個專案從需求訪談到驗收都是同一個工程師,真的效率太慢。看到妳提供了大量工具跟循序又分工的開發流程,真的學了很多。

謝謝你的肯定/images/emoticon/emoticon25.gif我之前在開發過程中遇到許多困難,也是身邊的工程師朋友推薦給我it邦和谷哥大神們的文章學習,現在又多了一個學習好幫手就是Chat GPT!

我要留言

立即登入留言